/**
 * The ItemManger is in charge manage items and caching Items, ItemClasses and Multimedia contents
 * 
 * @author remo.lemma@lu.unisi.ch and patrick.zulian@lu.unisi.ch
 */

package ch.unisi.inf.projects.java.uom.elements;

// Libraries
import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;
import java.util.Map;

import ch.unisi.inf.projects.java.uom.Database;
import ch.unisi.inf.projects.java.uom.ConstantArrays;
import ch.unisi.inf.projects.java.uom.Parser;

public class ItemManager {
	
    /** The only instance of ItemManager */
    private static ItemManager itemManager;
    
    /** Cache */
    private Cache cache;

    /** Counter of new cached and not saved items */
    private int newItemsCounter;
    
    /** List of listeners */
    private final List<ItemManagerListener> listeners;
    
    /** Represents the state of the database */
    private boolean dbOpen;
        
    /**
     * Constructor for ItemManger - Singleton
     */
    private ItemManager()
    {
        cache = new Cache();
        listeners = new ArrayList<ItemManagerListener>();
        newItemsCounter = 0;
        dbOpen = false;
    }
    
    /**
     * Returns the instance of ItemManager
     * @return the only instance of ItemManager
     */
    public static ItemManager instance()
    {
        if(itemManager == null)
            itemManager = new ItemManager();
        return itemManager;
    }
    
    /**
     * Creates a new Item (and notifies)
     * @param itemClass the ItemClass to which the Item belongs to
     * @return the created Item
     */
    public Item itemAdd (final ItemClass itemClass)
    {
    	return itemAdd(itemClass, true);
    }
    
    /**
     * Creates a new item (and notifies if it's the case)
     * @param itemClass the ItemClass to which the Item belongs to
     * @param notify a boolean that tells the method to notify listeners or not
     * @return the created Item
     */
    public Item itemAdd (final ItemClass itemClass, final boolean notify)
    {
    	return itemAdd(itemClass, (notify == true) ? 0 : 1);
    }
    	
    /**
     * Creates a new item and notifies according to the given parameter
     * @param itemClass the ItemClass to which the Item belongs to
     * @param notify an integer that is 0 to notify the item has been created, 1 to notify to focus on the item and any other value to not notify
     * @return the created Item
     */
    public Item itemAdd (final ItemClass itemClass, final int notify) 
    {
        final Item i = new Item(newItemsCounter++, itemClass);
        cache.putItem(i);
        switch (notify) {
        case 0:
        	fireItemAdded(i); break;
        case 1:
        	fireFocusOnItem(i); break;
        default:
        	break;
        }
        return i;
    }
    
    /**
     * Gets an Item from the database
     * @param id id of the Item
     * @param itemClass the ItemClass of the Item
     * @return the Item instance
     * @throws Exception handles the needed exceptions
     */
    public Item itemGet (final String id, final ItemClass itemClass) throws Exception
    {
    	if (id == null || id.equals(""))
    		return null;
        final Item item;
        if ((item = cache.getItem(id,itemClass)) != null)
            return (Item) item;
        final Item i = Database.instance().itemGet(id, itemClass);
        if (i != null)
        	cache.putItem(i);
        return i;
    }
    
    /**
     * Gets an Item from a database, starting from a reference key - value
     * @param field the field name
     * @param value the value (containing the id of the Item)
     * @return the required Item or null
     * @throws Exception handles the needed exceptions
     */
    public Item itemGet (final String field, final String value) throws Exception
    {
    	final String itemClassName = Parser.instance().dbGetVarType(field);
    	final String id = Parser.instance().dbGetVarName(value);
    	final ItemClass itemClass = this.itemClassGet(itemClassName);
    	if (itemClass == null)
    		return null;
    	return this.itemGet(id, itemClass);
    }
    
    /**
     * Creates a new ItemClass
     * @param name the name of the new ItemClass
     */
    public ItemClass itemClassAdd (final String name) throws Exception
    {
        final List<String> forbiddenClasses = Arrays.asList(ConstantArrays.ITEM_FORBIDDEN_ITEMCLASS.value());
        if (forbiddenClasses.contains(name) || cache.getItemClass(name) != null || Database.instance().itemClassIsThere(name))
            return null;
        final ItemClass itemClass = new ItemClass(name);
        cache.putItemClass(itemClass);
        fireItemClassAdded(itemClass);
        return itemClass;
    }
    
    /**
     * Gets an ItemClass from the database
     * @param name the name of the ItemClass
     * @return the ItemClass instance
     * @throws Exception handles the needed exceptions
     */
    public ItemClass itemClassGet (final String name) throws Exception
    {
        final ItemClass itemClass;
        if ((itemClass = cache.getItemClass(name)) != null)
            return (ItemClass) itemClass;
        final ItemClass iclass = Database.instance().itemClassGet(name);
        cache.putItemClass(iclass);
        return iclass;
    }
    
    /**
     * Saves an element into the database
     * @param element the element to be saved
     */
    public void elementSave (Element element) throws Exception
    {
        if (element != null && element.isModified()) {
            element.save();
            cache.saveElement(element);
            fireElementModified(element);
        }
    }
    
    /**
     * Deletes an element from the database
     * @param element the element to be deleted
     */
    public void elementDelete (Element element) throws Exception
    {
        if (element != null) {
            element.delete();
            cache.removeElement(element);
            fireElementDeleted(element);
        }
    }

    /**
     * Adds a field from to an element
     * @param e the element to operate with
     * @param field the field to be added
     * @param value the value of the field
     */
    public void elementAddField (final Element e, final String field, final String value)
    {
        if (e != null) {
            e.addField(field, value);
        }
    }
    
    /**
     * Sets a new value for the given field
     * @param e the element to operate with
     * @param field the field to remove
     * @param newField the new field value
     */
    public void elementSetField (final Element e, final String field, final String newField)
    {
        if (e != null && field != null && newField != null) {
            e.setField(field, newField);
        }
    }

    /**
     * Sets a new map of fields for the given element
     * @param e the element to operate with
     * @param fields the new map of fields
     */
    public void elementSetFields (final Element e, final Map<String,String> fields)
    {
        if (e != null && fields != null) {
            e.setFields(fields);
        }
    }
    
    /**
     * Removes a field from an element
     * @param e the element to operate with
     * @param field the field to remove
     */
    public void elementRemoveField (final Element e, final String field)
    {       
        if (e != null && field != null) {
            e.removeField(field);
        }
    }
    
    /**
     * Returns a set of all ItemClasses in the database
     * @return the set of ItemClasses
	 * @throws Exception handles the needed exceptions
     */
    public Set<ItemClass> getItemClasses () throws Exception
    {
        final Set<ItemClass> itemClasses;
        if (cache.hasItemClasses())
        	itemClasses = cache.getItemClasses();
        else
        	itemClasses = Database.instance().getItemClasses();
        final Set<ItemClass> realItemClasses = new HashSet<ItemClass>();
        final List<String> forbiddenClasses = Arrays.asList(ConstantArrays.ITEM_FORBIDDEN_ITEMCLASS.value());
        for (ItemClass c : itemClasses) {
        	cache.putItemClass(c);
            if (!forbiddenClasses.contains(c.getName()))
                realItemClasses.add(c);
        }
        cache.setHasItemClasses(true);
        return realItemClasses;
    }
    
    /**
     * Returns the set of Items for the given ItemClass
     * @param itemClass the ItemClas of the desired Items
     * @return the set of Items
	 * @throws Exception handles the needed exceptions
     */
    public Set<Item> getItemsFromItemClass (final ItemClass itemClass) throws Exception
    {
    	if (cache.alreadySeen(itemClass))
    		return cache.getItemsFromItemClass(itemClass);
        final Set<Item> items = Database.instance().getItemsFromItemClass(itemClass);
        for (final Item item : items)
        	cache.putItem(item);
        cache.setSeen(itemClass);
        return items;
    }
    
    /** 
     * Gets all the items from the database 
     * @return the set of items  
     */
    
    public Set<Item> getItems() throws Exception
    {
    	if (cache.hasAll())
    		return cache.getItems();
        final Set<Item> items = Database.instance().getItems();
        return items;
    }
    
    /**
     * Gets a Multimedia from the database
     * @param id the id of the Multimedia
     * @return the Multimedia object
     * @throws Exception handles the needed exceptions
     */
    public Multimedia multimediaFromId (final String id) throws Exception
    {
        final Object multimedia;
        if ((multimedia = cache.getMultimedia(id)) != null)
            return (Multimedia) multimedia;
    	final Multimedia m = Multimedia.getFromId(id);
    	cache.addMultimedia(m);
    	return m;
    }
    
    /**
     * Saves a multimedia to the database
     * @param m the multimedia
     */
    public void multimediaSave (final Multimedia m)
    {
    	if (m != null && m.isModified()) {
    		m.save();
    		cache.addMultimedia(m);
    	}
    }
    
    /**
     * Deletes a multimedia from the database
     * @param m the multimedia
     */
    public void multimediaDelete (final Multimedia m)
    {
    	if (m != null) {
    		cache.removeMultimedia(m);
    		m.delete();
    	}
    }
    
    /**
     * Erases the cache
     */
    public void clear ()
    {
    	dbOpen = false;
        cache.clear();
    }
    
    /**
     * Tells the ItemManager that the database has been opened
     */
    public void databaseHasOpened ()
    {
    	dbOpen = true;
    	fireDatabaseOpened();
    }
    
    /**
     * Checks if the database is open
     * @return true if the database is open false otherwise
     */
    public boolean databaseIsOpen ()
    {
    	return dbOpen; 
    }
    
    /**
     * Tells the ItemManager that the database has been closed
     */
    public void databaseClosed()
    {
    	// Stuff
    	this.clear();
    	fireDatabaseClosed();
    }
    
    /**
     * Adds a listener
     * @param listener the listener to be added
     */
    public void addItemManagerListener (final ItemManagerListener listener)
    {
    	listeners.add(listener);
    }
    
    /**
     * Removes a listener
     * @param listener the listener to be removed
     */
    public void removeItemManagerListener (final ItemManagerListener listener)
    {
    	listeners.remove(listener);
    }
    
    /**
     * Signals that an Item has been added
     * @param i the added Item
     */
    private void fireItemAdded (final Item i)
    {
    	for (final ItemManagerListener li : listeners)
    		li.itemAdded(i);
    }
    
    /**
     * Signals that an ItemClass has been added
     * @param c the added ItemClass
     */
    private void fireItemClassAdded (final ItemClass c)
    {
    	for (final ItemManagerListener li : listeners)
    		li.itemClassAdded(c);
    }
    
    /**
     * Signals that an element has been deleted
     * @param e the element that has been deleted
     */
    private void fireElementDeleted (final Element e)
    {
    	for (final ItemManagerListener li : listeners)
    		li.elementDeleted(e);
    }
    
    /**
     * Signals that an element has changed
     * @param e the element that has been modified
     */
    private void fireElementModified (final Element e)
    {
    	for (final ItemManagerListener li : listeners)
    		li.elementModified(e);
    }
    
    /**
     * Signals that a new database has been opened
     */
    private void fireDatabaseOpened ()
    {
    	for (final ItemManagerListener li : listeners)
    		li.databaseOpened();
    }
    
    /**
     * Signals that a database is being closed
     */
    private void fireDatabaseClosed ()
    {
    	for (final ItemManagerListener li : listeners)
    		li.databaseClosed();
    }
    
    /**
     * Signals to focus on the given item
     * @param i the item to be focused on
     */
    public void fireFocusOnItem (final Item i) {
    	for (final ItemManagerListener li : listeners)
    		li.focusOnItem(i);
    }
}